/*
 * @Author: LVGRAPE
 * @LastEditors: LVGRAPE
 */

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <windows.h>
#include "bootloader.h"
#include "uart.h"
#include "at32_mcu_info.h"
#include "iap_protocol.h"

#define CRC32_INIT_VALUE 0xFFFFFFFF
#define CRC32_XOR_VALUE 0xFFFFFFFF
void delay_ms(uint32_t ms);
uint32_t crc32_cal(const uint8_t *pdata, int32_t len, uint32_t initial, uint32_t finalXor, bool inputReflected, bool resultReflected);

firmware_info_t firmware;
bl_info_t bootloader;
bl_info_t *p_bl = &bootloader;
uint8_t *firmware_data = NULL;
// int f_com = 0;
uint16_t SetSize = 0;
int readout = 0;

void hardwareReset(void)
{
    uart_win_set_rts(0, 1);
    delay_ms(5);
    uart_win_set_rts(0, 0);
    delay_ms(5);
    uart_win_set_rts(0, 1);
    delay_ms(5);
    // uart_win_set_rts(0,0);
    // delay_ms(5);
    // uart_win_set_rts(0,1);
}

int firmware_extract(FILE *f, firmware_info_t *i)
{
    if (!f)
        return -1;
    size_t readsize = fread(i, 1, sizeof(firmware_info_t), f);
    if (readsize != sizeof(firmware_info_t))
    {
        printf("firmware_extract error, readsize:%d\n", readsize);
        return -1;
    }

    // printf("readsize:%d\n",readsize);
    uint32_t crc = crc32_cal((const uint8_t *)i + 4, sizeof(firmware_info_t) - 4, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
    if (crc != i->infoCrc)
    {
        printf("firmware_extract error, crc: 0x%08X, infoCrc: 0x%08X\n", crc, i->infoCrc);
        return -1;
    }
    printf(" *name     : %s\n", i->name);
    printf(" *infoCrc  : 0x%08X\n", i->infoCrc);
    printf(" *magic    : 0x%08X\n", i->magic);
    printf(" *startAddr: 0x%08X\n", i->start_addr);
    printf(" *size     : 0x%08X\n", i->size);
    printf(" *FWcrc    : 0x%08X\n", i->FWcrc);
    printf(" *version  : 0x%08X\n", i->version);
    printf(" *pid      : 0x%08X\n", i->pid);
    printf(" *date     : 0x%08X\n", i->date);
    printf(" *DeviceID : 0x%016llX\n", i->DeviceID);
    // printf(" *whoAmI   : 0x%08X\n", i->whoAmI.whoAmI);

    // printf(" *Serial   : 0x%08X\n", i->whoAmI.Serial);
    // printf(" *ID       : 0x%08X\n", i->whoAmI.ID);
    // printf(" *Model    : 0x%08X\n", i->whoAmI.Model);
    // printf(" *Flash    : 0x%08X\n", i->whoAmI.Flash);
    // printf(" *Footprint: 0x%08X\n", i->whoAmI.Footprint);

    firmware_data = (uint8_t *)malloc(i->size);
    if (!firmware_data)
    {
        printf("firmware_extract error, malloc failed\n");
        return -1;
    }
    readsize = fread(firmware_data, 1, i->size, f);

    // FILE *fw = fopen("./write.bin", "wb");
    // fwrite(firmware_data, 1, i->size, fw);
    // fclose(fw);

    if (readsize != i->size)
    {
        printf("firmware_extract error, readsize:%d/%d\n", readsize, i->size);
        return -1;
    }
    crc = crc32_cal(firmware_data, i->size, CRC32_INIT_VALUE, CRC32_XOR_VALUE, 1, 1);
    if (crc != i->FWcrc)
    {
        printf("firmware_extract error, crc:%x, FWcrc:%x\n", crc, i->FWcrc);
        return -1;
    }
    printf(" *FWcrc: 0x%08X\n", crc);
    printf("Done\n\n");
    return 0;
}
void delay_ms(uint32_t ms)
{
    Sleep(ms);
}

/**
 * @brief
 * make -f ./iap_loader/makefile
 * ./win_iap_tool/win_iap_tool.exe COM1 921600 "./ZINO PREMIUM RT DRONE V4.0.0.0.bin"
 * ./win_iap_tool/win_iap_tool.exe COM5 921600 "./ZINO_PREMIUM_ZINO_FC_V4_V1.0.1_20241119.bin" verify
 * ./win_iap_tool/win_iap_tool.exe COM3 921600 "./ZINO_PREMIUM_ZINO_FC_V4_V1.0.1_20241120.bin"
 * ./tool/at32_iap_loader_v3_win.exe COM3 921600 "./GuoLiForkLift_ZINO_GuoLiForkLift_V1.0.0_20250127.bin"
 * @param argc 4
 * @param argv [com] [baudrate] [firmware]
 * @return int
 */
int main(int argc, char *argv[])
{
    bootloader.read = uart_win_read;
    bootloader.write = uart_win_write;
    bootloader.clearRx = uart_win_clear;
    bool verify_flag = false;
    if (argc < 4)
    {
        printf("Usage:%s [com] [baudrate] [firmware]\n", argv[0]);
        printf("Example:\n ./win_iap_tool COM9 921600 \"./ZINO PREMIUM RT DRONE V4.0.0.0.bin\"\n");
        return -1;
    }

    if (argc >= 5)
    {
        // SetSize = atoi(argv[4]);
        if(strcmp(argv[4],"verify") == 0)
        {
            verify_flag = true;
        }
    }
    if (argc >= 6)
    {
        readout = atoi(argv[5]);
    }
    printf("\n\n * * * * * * ZINO Firmware Burning Tool V2.0.0 * * * * * * \n\n");

    /**serial port init */
    int baudrate = atoi(argv[2]);
    char *com = argv[1];
    // if (serial_init(&f_com, com, baudrate) != 0)
    // {
    //     printf("serial init error!\n");
    //     return -1;
    // }
    if (uart_win_init(com, baudrate) != 0)
    {
        printf("serial init error!\n");
        return -1;
    }

    // 判断文件尾缀是否为.bin
    char *fw_file = argv[3];
    int fw_file_len = strlen(fw_file);
    if (strcmp(fw_file + fw_file_len - 4, ".bin") != 0)
    {
        printf("bin file error! Only accept *.bin file!\n");
        return -1;
    }

    // printf("\nbin file:%s\n", fw_file);

    FILE *f_fw = fopen(fw_file, "r");
    if (f_fw == NULL)
    {
        printf("bin file open error! %s\n", strerror(errno));
        return -1;
    }
    printf("firmware extract...\n");
    if (firmware_extract(f_fw, &firmware) != 0)
    {
        printf("firmware extract error!\n");
        return -1;
    }

    uint32_t sector_size = at32_get_flash_size(firmware.DeviceID);
    if (sector_size == 0)
    {
        printf("get flash size error!\n");
        return -1;
    }
    if (sector_size <= 128 * 1024)
    {
        p_bl->sectorSize = 1024;
    }
    else if (sector_size >= 256 && sector_size <= 512 * 1024)
    {
        p_bl->sectorSize = 2048;
    }
    else if (sector_size >= 1024 * 1024)
    {
        p_bl->sectorSize = 4096;
    }
    printf("flash size: %db(%dKb), sector size: %db\n\n", sector_size, sector_size / 1024, p_bl->sectorSize);
    uint32_t read_size = 0;
    uint32_t total_write = 0;
    uint32_t total_pack = firmware.size / sector_size + (firmware.size % sector_size ? 1 : 0);
    uint32_t pack_count = 0;
    uint32_t write_addr = firmware.start_addr;
    uint32_t sector_count = 0;
    // printf("total_pack:%d\n", total_pack);

    timer_t startTime = time(NULL);
    // timer_t frameTime = time(NULL);
    // timer_t frameTimeUsed = time(NULL);
    // timer_t totalTime = time(NULL);

    printf("start burning firmware...\n");

    hardwareReset();

    /**NOTE SYNC */
    if (dev_sync(p_bl) != 0)
    {
        printf("dev_sync error!\n");
        return -1;
    }
    /**NOTE GET VERSION */
    if (getVersion(p_bl) != 0)
    {
        printf("dev_get_version error!\n");
        return -1;
    }
    /**NOTE GET ID */
    if (getId(p_bl) != 0)
    {
        printf("dev_get_id error!\n");
        return -1;
    }

    if (p_bl->DeviceID != firmware.DeviceID)
    {
        printf("DeviceID not mactch!\n");
        printf("target   DeviceID: [%016llX]\n", p_bl->DeviceID);
        printf("firmware DeviceID: [%016llX]\n", firmware.DeviceID);
        return -1;
    }
    else
    {
        printf("DeviceID mactch!\n");
        printf("target   DeviceID:%016llX\n", p_bl->DeviceID);
        printf("firmware DeviceID:%016llX\n\n", firmware.DeviceID);
    }

    /**NOTE GET fwInfo */
    if (get_fwInfo(p_bl) != 0)
    {
        printf("dev_get_fwinfo error!\n");
        return -1;
    }
    if (p_bl->fw_info.pid != firmware.pid)
    {
        int result = MessageBox(NULL, "PID not mactch!\nDo you want to continue?", "Warning", MB_YESNO | MB_ICONWARNING);
        if (result != IDYES)
        {
            printf("PID old[%08X] - new[%08X] not mactch!\n abort!\n", p_bl->fw_info.pid, firmware.pid);
            return -1;
        }
        else
        {
            printf("PID update old[%08X] -> new[%08X]\n\n", p_bl->fw_info.pid, firmware.pid);
        }
    }
    if (p_bl->fw_info.version > firmware.version)
    {
        int result = MessageBox(NULL, "Target version is newer than firmware!\nDo you want to continue?", "Warning", MB_YESNO | MB_ICONWARNING);
        if (result != IDYES)
        {
            printf("Version old[%08X] - new[%08X] not mactch!\n abort!\n", p_bl->fw_info.version, firmware.version);
            return -1;
        }
        else
        {
            printf("Version downgrade old[%08X] -> new[%08X]\n\n", p_bl->fw_info.version, firmware.version);
        }
    }
    else
    {
        printf("version upgrade old[%08X] -> new[%08X]\n\n", p_bl->fw_info.version, firmware.version);
    }
    if (p_bl->fw_info.date > firmware.date)
    {
        int result = MessageBox(NULL, "Target date is newer than firmware!\nDo you want to continue?", "Warning", MB_YESNO | MB_ICONWARNING);
        if (result != IDYES)
        {
            printf("Date old[%04X] - new[%04X] not mactch!\n abort!\n", p_bl->fw_info.date, firmware.date);
            return -1;
        }
        else
        {
            printf("Date update old[%04X] -> new[%04X]\n\n", p_bl->fw_info.date, firmware.date);
        }
    }

    /**NOTE ERASE FLASH */
    if (erase_flash(p_bl, &firmware) != 0)
    {
        printf("erase_flash error!\n");
        return -1;
    }
    /** NOTE WRITE FLASH */
    if (write_firmware(p_bl, &firmware, firmware_data) != 0)
    {
        printf("write_firmware error!\n");
        return -1;
    }
    /** NOTE read FLASH */
    if (verify_flag != 0)
    {
        if (read_firmware(p_bl, &firmware, "./firmwareReadout.bin") != 0)
        {
            printf("read_firmware error!\n");
            return -1;
        }
    }
    /**NOTE write fwInfo */
    if (write_fwInfo(p_bl, &firmware) != 0)
    {
        printf("write_fwInfo error!\n");
        return -1;
    }
    /**NOTE RESET */
    hardwareReset();
    // if (reset(p_bl) != 0)
    // {
    //     printf("reset error!\n");
    //     return -1;
    // }
    struct tm sTime;
    struct tm eTime;
    timer_t endTime = time(NULL);
    localtime_r(&startTime, &sTime);
    localtime_r(&endTime, &eTime);
    timer_t totalTime = endTime - startTime;
    printf("Start time: %d-%02d-%02d %02d:%02d:%02d\n", sTime.tm_year + 1900, sTime.tm_mon + 1, sTime.tm_mday, sTime.tm_hour, sTime.tm_min, sTime.tm_sec);
    printf("End   time: %d-%02d-%02d %02d:%02d:%02d\n", eTime.tm_year + 1900, eTime.tm_mon + 1, eTime.tm_mday, eTime.tm_hour, eTime.tm_min, eTime.tm_sec);
    // printf("End time:%s\n", ctime(&endTime));
    printf("Total time: %ds\n\n", totalTime);
    printf("OK OK OK OK OK OK OK OK OK OK !\n");
    free(firmware_data);
    return 0;
}
